package com.feilong.modules.service.goods.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.feilong.modules.client.auth.AuthClient;
import com.feilong.modules.common.constant.UploadConstant;
import com.feilong.modules.common.exception.APIException;
import com.feilong.modules.common.request.WxOperateDbRequest;
import com.feilong.modules.common.utils.ImageUtils;
import com.feilong.modules.dao.goods.GoodsInfoDao;
import com.feilong.modules.model.goods.constant.GoodsInfoConstant;
import com.feilong.modules.model.goods.enums.IsSellEnum;
import com.feilong.modules.model.goods.po.GoodsInfoPO;
import com.feilong.modules.model.goods.request.GetUploadRequest;
import com.feilong.modules.model.goods.request.QueryGoodsRequest;
import com.feilong.modules.model.goods.vo.GoodsDetailVO;
import com.feilong.modules.model.goods.vo.GoodsInfoVO;
import com.feilong.modules.service.goods.GoodsCategoryService;
import com.feilong.modules.service.goods.GoodsInfoService;
import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Service
@Slf4j
public class GoodsInfoServiceImpl extends ServiceImpl<GoodsInfoDao, GoodsInfoPO> implements GoodsInfoService {

    @Value("${feilongshop.config.appid}")
    private String APPID;

    @Value("${feilongshop.config.app-secret}")
    private String APPSECRET;

    @Value("${feilongshop.config.env}")
    private String ENV;

    @Value("${feilongshop.config.file-prefix}")
    private String FILE_PREFIX;

    @Value("${feilongshop.wx-cloud.goods-image-path-prefix}")
    private String IMAGE_PATH_PREFIX;

    /**
     * Redis中获取AccessToken的Key
     */
    private static final String ACCESS_TOKEN_KEY = "feilongshop:wxCloud:accessToken";

    /**
     * Redis中获取商品信息的Key
     */
    private static final String GOODS_INFO_KEY = "feilongshop:wxCloud:goodsInfo:%s";

    /**
     * 官方给的有效时长是7200秒，预留传输时间-200秒
     */
    private static final Integer ACCESS_TOKEN_EXPIRED_TIME = 7000;

    @Resource
    private AuthClient authClient;

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private GoodsCategoryService goodsCategoryService;

    /**
     * 上传商品图片到微信云开发服务器
     * @param file
     * @param fileName
     */
    @Override
    public String uploadGoodsImage(MultipartFile file, String fileName){
        // 校验图片格式，只能上传jpg、jpeg、png、gif
        if (!ImageUtils.verifyImageSuffix(file)) {
            throw new APIException(UploadConstant.IMAGE_FORMAT_ERROR);
        }
        LocalDateTime now = LocalDateTime.now();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        String time = now.format(formatter);
        // 获取文件后缀名
        String ext = StringUtils.substringAfterLast(file.getOriginalFilename(),".");
        // 根据fileName和后缀名拼接图片上传至微信云开服服务器的path
        // 拼上时间，防止重复上传回显读上一张图片的问题
        String path = IMAGE_PATH_PREFIX + fileName + " " + time + "." + ext;
        // 获取AccessToken
        String token = getAccessToken();
        GetUploadRequest getUploadRequest = new GetUploadRequest();
        getUploadRequest.setEnv(ENV);
        getUploadRequest.setPath(path);
        // 获取上传链接
        String result = authClient.getUploadUrl(getUploadRequest,token);
        JSONObject data = JSONObject.parseObject(result);
        // 上传图片
        return uploadImage(file, data, path);
    }

    /**
     * 从微信云开发服务器获取商品信息
     * @param id
     * @return
     */
    @Override
    public GoodsDetailVO getGoodsInfo(Integer id) {
        // 先从redis中获取数据，若没有，再请求微信云开发服务器
        String data = (String) redisTemplate.opsForValue().get(String.format(GOODS_INFO_KEY,id));
        if (StringUtils.isNotBlank(data)) {
            GoodsDetailVO goodsInfo = null;
            try {
                goodsInfo = JSON.parseObject(data,GoodsDetailVO.class);
            } catch (Exception e) {
                log.error("从redis中取出商品信息出错，JSON转换成对象异常，报错信息 -> {}", e.toString());
            }
            return goodsInfo;
        }
        // redis中无数据，从微信云开发服务器获取商品信息
        // 获取AccessToken
        String token = getAccessToken();
        WxOperateDbRequest request = new WxOperateDbRequest();
        request.setEnv(ENV);
        request.setQuery("db.collection(\"goods\").where({goods_id:" + id + "}).get()");
        String result = authClient.queryCloudDbInfo(request, token);
        // 返回的数据中的data里包含 \ 字符，去掉才能方便取值
        result = StringEscapeUtils.unescapeJava(result);
        // 需要获取数据中的data:["{...}"]大括号和大括号里面的值
        GoodsDetailVO goodsInfo = getData(result);
        // 查找商品分类信息
        List<Integer> categoryIds = goodsCategoryService.getGoodsCategoryId(id);
        goodsInfo.setCategoryId(categoryIds);
        // 商品信息存入redis
        redisTemplate.opsForValue().set(String.format(GOODS_INFO_KEY,id),JSON.toJSONString(goodsInfo));
        return goodsInfo;
    }

    /**
     * 在微信云开发服务器添加商品信息
     * @param goodsInfo
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addGoodsInfo(GoodsDetailVO goodsInfo) {
        // 获取mysql数据库中商品主表的最大id值+1并添加到微信云数据库的商品id里
        Integer id = this.getOne(new QueryWrapper<GoodsInfoPO>().select("MAX(id) id")).getId();
        goodsInfo.setGoodsId(id + 1);
        // 获取AccessToken
        String token = getAccessToken();
        WxOperateDbRequest request = new WxOperateDbRequest();
        request.setEnv(ENV);
        request.setQuery("db.collection(\"goods\").add({ data: [" + JSONObject.toJSONString(goodsInfo) + "]})");
        // MongoDB添加商品
        authClient.insertCloudDbInfo(request,token);
        // 在mysql数据库商品info表添加信息
        this.save(GoodsInfoPO.builder()
                .createTime(new Date())
                .goodsLogo(goodsInfo.getGoodsLogo())
                .goodsName(goodsInfo.getGoodsName())
                .goodsPrice(goodsInfo.getGoodsPrice())
                .postageType(goodsInfo.getPostageType())
                .build()
        );
    }

    /**
     * 更新微信云开发服务器商品信息
     * @param goodsInfo
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateGoodsInfo(GoodsDetailVO goodsInfo) {
        if (goodsInfo.getGoodsId() == null) {
            throw new APIException(GoodsInfoConstant.GOODS_UPDATE_FAIL);
        }
        // 获取AccessToken
        String token = getAccessToken();
        WxOperateDbRequest request = new WxOperateDbRequest();
        request.setEnv(ENV);
        // MongoDB更新商品信息
        request.setQuery("db.collection(\"goods\").where({goods_id:" + goodsInfo.getGoodsId() +"}).update({ data: " + JSONObject.toJSONString(goodsInfo) + "})");
        String result = authClient.updateCloudDbInfo(request,token);
        log.info("更新商品： {}, 更新结果：{}", goodsInfo.getGoodsName(), result);
        // 更新mysql数据库商品信息表
        this.updateById(GoodsInfoPO.builder()
                .id(goodsInfo.getGoodsId())
                .goodsName(goodsInfo.getGoodsName())
                .postageType(goodsInfo.getPostageType())
                .goodsPrice(goodsInfo.getGoodsPrice())
                .goodsLogo(goodsInfo.getGoodsLogo())
                .isSell(goodsInfo.getIsSell())
                .build()
        );
        // 更新redis中的缓存数据
        redisTemplate.opsForValue().set(String.format(GOODS_INFO_KEY,goodsInfo.getGoodsId()),JSON.toJSONString(goodsInfo));
    }

    /**
     * 从mysql中获取商品列表或者查询商品
     * @param query
     * @return
     */
    @Override
    public Page<GoodsInfoVO> getGoodsList(QueryGoodsRequest query) {
        Page<GoodsInfoVO> page = new Page<>(query.getCurrent(), query.getSize());
        List<GoodsInfoVO> goodsInfoList = baseMapper.getGoodsList(query,page);
        page.setRecords(goodsInfoList);
        return page;
    }

    /**
     * 不分页的形式根据商品名称获取商品列表
     * @param goodsName
     * @return
     */
    @Override
    public List<GoodsInfoVO> getGoodsByName(String goodsName) {
        List<GoodsInfoPO> list = this.list(new LambdaQueryWrapper<GoodsInfoPO>()
                .like(GoodsInfoPO::getGoodsName, goodsName)
                .eq(GoodsInfoPO::getIsSell, IsSellEnum.SELL.getCode())
        );
        List<GoodsInfoVO> goodsList = new ArrayList<>();
        if (!CollectionUtils.isEmpty(list)) {
            list.forEach(item ->{
                GoodsInfoVO goodsInfoVO = new GoodsInfoVO();
                BeanUtils.copyProperties(item, goodsInfoVO);
                goodsList.add(goodsInfoVO);
            });
        }
        return goodsList;
    }

    /**
     * 针对微信返回值的特殊处理
     * @param result
     */
    private GoodsDetailVO getData(String result) {
        //找到第一个 [" 后面的 { 和最后一个 "] 前面的 } 并截取
        int firstIndex = result.indexOf("[") + 2;
        int lastIndex = result.lastIndexOf("]") - 1;
        JSONObject jsonObject = JSONObject.parseObject(result.substring(firstIndex, lastIndex));
        GoodsDetailVO goodsDetailVO = jsonObject.toJavaObject(GoodsDetailVO.class);
        return goodsDetailVO;
    }

    /**
     * RestTemplate方式发送Post请求上传图片
     * @param file
     * @param data
     * @param path
     */
    public String uploadImage(MultipartFile file, JSONObject data, String path){
        try{
            // 处理上传url，原本返回的url中文名称会进行编码，需要替换编码为中文名称
            String fileName = StringUtils.substringAfterLast(path,"/");
            String url = StringUtils.substringBeforeLast(data.getString("url"),"/") + "/" + fileName;
            RestTemplate restTemplate = new RestTemplate();
            HttpHeaders headers = new HttpHeaders();
            MultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
            map.add("key",path);
            map.add("Signature",data.getString("authorization"));
            map.add("x-cos-security-token",data.getString("token"));
            map.add("x-cos-meta-fileid",data.getString("cos_file_id"));
            map.add("file",file.getBytes());
            headers.setContentType(MediaType.MULTIPART_FORM_DATA);
            HttpEntity<MultiValueMap<String, Object>> param = new HttpEntity<>(map, headers);
            //发起请求，服务地址，请求参数，返回消息体的数据类型
            ResponseEntity<String> response = restTemplate.postForEntity(url, param, String.class);
            //body
            String body = response.getBody();
            log.info(url + " 请求返回值： " + body);
        } catch (Exception e) {
            log.error("上传图片失败，失败原因： " + e);
            throw new APIException(UploadConstant.IMAGE_UPLOAD_FAIL);
        }
        return FILE_PREFIX + path;
    }

    /**
     * 获取小程序AccessToken
     * @return
     */
    public String getAccessToken(){
        String result = authClient.getAccessToken(APPID,APPSECRET);
        JSONObject data = JSONObject.parseObject(result);
        String token = data.getString("access_token");
        log.info("获取到的微信云开发AccessToken为 -> {}", token);
        return token;
    }

    /**
     * 压缩图片
     * @param multipartFile
     * @return
     */
    public InputStream compressImage(MultipartFile multipartFile) {

        //*************对不是jpg格式的图片转换成jpg格式***************
        //获取文件名后缀，判断其格式
        int begin = multipartFile.getOriginalFilename().lastIndexOf(".");
        int last = multipartFile.getOriginalFilename().length();
        //获得文件后缀名
        String houzuiFileName = multipartFile.getOriginalFilename().substring(begin, last);

        //创建临时文件
        File tempFile = new File(multipartFile.getOriginalFilename());
        //写入临时File文件 tempFile，将multipartFile转换成File
        try {
            //import org.apache.commons.io.FileUtils;
            FileUtils.copyInputStreamToFile(multipartFile.getInputStream(), tempFile);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //如果文件不是jpg格式,转换其格式
        if (!"jpg".equalsIgnoreCase(houzuiFileName)) {
            //ImageUtils是一个工具类，下面给出
            //将png格式转换成jpg，输出到tempFile
            ImageUtils.convert(multipartFile.getOriginalFilename(), "jpg", tempFile.getAbsolutePath());//测试OK
            //对不是jpg格式的图片转换成jpg格式
        }
        try {
            //压缩图片
            BufferedImage bufferedImage = Thumbnails.of(tempFile)
                    .size(740, 1330)//指定压缩之后的图片尺寸
                    .outputQuality(0.8f)//图片压缩质量
                    .asBufferedImage();
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            ImageIO.write(bufferedImage, "jpg", os);
            InputStream inputStream = new ByteArrayInputStream(os.toByteArray());
            return inputStream;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //会在本地产生临时文件，用完后需要删除
            if (tempFile.exists()) {
                tempFile.delete();
            }
        }
        return null;
    }

}
